package com.gitee.sop.mongodb;

import com.alibaba.fastjson.JSONObject;
import com.mongodb.client.MongoCollection;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.*;

/**
 * @author: Admin
 * @date: 2020/11/25
 * @description:
 * @copyright: 重庆万蓝兴科技有限公司
 */
@Component
public class MongoDbUtil {

    @Resource
    private MongoTemplate mongoTemplate;

    /**
     * 指定的集合是否存在
     *
     * @param collectionName 集合名称
     * @return 是否
     */
    public boolean collectionExists(String collectionName) {
        return mongoTemplate.collectionExists(collectionName);
    }

    /**
     * 创建集合
     *
     * @param collectionName 集合名称
     * @return 是否
     */
    public boolean createCollection(String collectionName) {
        MongoCollection<Document> collection = mongoTemplate.createCollection(collectionName);
        return Objects.nonNull(collection);
    }

    /**
     * 批量插入数据
     *
     * @param collectionName 集合名称
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 返回数据集合
     */
    public <T> List<T> insertDocument(String collectionName, List<T> list) {
        return (List<T>) mongoTemplate.insert(list, collectionName);
    }

    /**
     * 修改指定数据
     *
     * @param collectionName 集合名称
     * @param uniqueKey      主键key
     * @param uniqueValue    主键值
     * @param updateMap      修改参数
     */
    public void updateOne(String collectionName, String uniqueKey, String uniqueValue, Map<String, Object> updateMap) {
        Query query = Query.query(Criteria.where(uniqueKey).is(uniqueValue));
        Set<String> keySet = updateMap.keySet();
        Update update = new Update();
        for (String key : keySet) {
            update.set(key, updateMap.get(key));
        }
        mongoTemplate.updateFirst(query, update, Map.class, collectionName);
    }

    /**
     * 批量删除
     *
     * @param collectionName 集合名称
     * @param uniqueKey      主键key
     * @param uniqueValues   待删除的主键值集合
     */
    public void deleteDocument(String collectionName, String uniqueKey, List<Object> uniqueValues) {
        uniqueValues.forEach(value -> {
            Query query = Query.query(Criteria.where(uniqueKey).is(value));
            mongoTemplate.remove(query, collectionName);
        });

    }

    /**
     * 查询指定的数据
     *
     * @param collectionName 集合名称
     * @param queryParams    查询参数
     * @param entityClass    数据类型
     * @param <T>            数据类型
     * @return
     */
    public <T> T queryOne(String collectionName, List<MongoQuery> queryParams, Class<T> entityClass) {
        Query query = loadQueryParam(queryParams);
        return mongoTemplate.findOne(query, entityClass, collectionName);
    }

    /**
     * 分页模糊排序查询
     *
     * @param collectionName 集合名称
     * @param queryParams    查询参数
     * @param sortParams     排序参数 key排序字段，value为 升序asc ，降序desc
     * @param start          开始数
     * @param limit          限制数
     * @param cls            转换的类型
     * @return
     */
    public MongoDbResult queryByParameter(String collectionName, List<MongoQuery> queryParams, Map<String, String> sortParams, int start, int limit, Class cls) {
        MongoDbResult page = new MongoDbResult(start, limit);
        long count = queryCountByParameter(collectionName, queryParams);
        if (count == 0) {
            return page;
        }
        //当前页处理： 由于从0角标开始
        if (start > 1) {
            start = start * limit - 2;
        } else {
            start = 0;
        }

        Query query = this.loadQueryParam(queryParams);
        if (Objects.nonNull(sortParams)) {
            //排序
            List<Sort.Order> orders = new ArrayList<>();
            sortParams.forEach((k, v) -> {
                if ("asc".equalsIgnoreCase(v)) {
                    Sort.Order order = Sort.Order.asc(k);
                    orders.add(order);
                } else {
                    Sort.Order order = Sort.Order.desc(k);
                    orders.add(order);
                }
            });
            if (orders.size() > 0) {
                query.with(Sort.by(orders));
            }
        }
        query.skip(start).limit(limit);
        List list = mongoTemplate.find(query, cls, collectionName);
        page.setRecords(list).setTotal(count);
        return page;
    }

    /**
     * 查询指定的数据数量
     *
     * @param collectionName 集合名称
     * @param queryParams    查询参数
     * @return 数量
     */
    public long queryCountByParameter(String collectionName, List<MongoQuery> queryParams) {
        Query query = this.loadQueryParam(queryParams);
        return mongoTemplate.count(query, collectionName);
    }

    /**
     * 通用查询参数
     *
     * @param queryParams 参数集合
     * @return
     */
    private Query loadQueryParam(List<MongoQuery> queryParams) {
        Criteria criteria = new Criteria();
        //查询参数
        if (Objects.nonNull(queryParams)) {
            queryParams.forEach(queryParam -> {
                String key = queryParam.getKey();
                Object value = queryParam.getValue();
                boolean fuzzy = queryParam.isFuzzy();
                if (fuzzy) {
                    criteria.and(key).regex(".*?" + value + ".*?");
                } else {
                    criteria.and(key).is(value);
                }
            });
        }
        Query query = Query.query(criteria);
        return query;
    }

    /**
     * 分页模糊排序查询
     *
     * @param collectionName 集合名称
     * @param queryParams    查询参数
     * @param sortParams     排序参数 key排序字段，value为 升序asc ，降序desc
     * @param start          开始数
     * @param limit          限制数
     * @param cls            转换的类型
     * @return
     */
    public MongoDbResult queryByParameterLike(String collectionName, Set<MongoLike> queryParams, Map<String, String> sortParams, int start, int limit, Class cls) {
        MongoDbResult page = new MongoDbResult(start, limit);
        long count = queryCountByParameterLike(collectionName, queryParams);
        if (count == 0) {
            return page;
        }
        //当前页处理： 由于从0角标开始
        if (start > 1) {
            start = start * limit - 2;
        } else {
            start = 0;
        }

        Query query = this.dealQuery(queryParams);
        if (Objects.nonNull(sortParams)) {
            //排序
            List<Sort.Order> orders = new ArrayList<>();
            sortParams.forEach((k, v) -> {
                if ("asc".equalsIgnoreCase(v)) {
                    Sort.Order order = Sort.Order.asc(k);
                    orders.add(order);
                } else {
                    Sort.Order order = Sort.Order.desc(k);
                    orders.add(order);
                }
            });
            if (orders.size() > 0) {
                query.with(Sort.by(orders));
            }
        }
        query.skip(start).limit(limit);
        List list = mongoTemplate.find(query, cls, collectionName);
        page.setRecords(list).setTotal(count);
        return page;
    }


    /**
     * 查询指定的数据数量
     *
     * @param collectionName 集合名称
     * @param queryParams    查询参数
     * @return 数量
     */
    public long queryCountByParameterLike(String collectionName, Set<MongoLike> queryParams) {
        Query query = this.dealQuery(queryParams);
        return mongoTemplate.count(query, collectionName);
    }

    /**
     * 处理动态查询参数
     *
     * @param queryParams 查询参数
     * @return
     */
    private Query dealQuery(Set<MongoLike> queryParams) {
        List<Criteria> operations = new ArrayList<>();
        if (Objects.nonNull(queryParams) && queryParams.size() > 0) {
            queryParams.forEach(mongoLike -> {
                String match = mongoLike.getMatch();
                String key = mongoLike.getKey();
                String type = mongoLike.getType();
                Object value = mongoLike.getValue();
                List valueList = mongoLike.getValueList();
                if ((Objects.isNull(value) || StringUtils.isBlank(value.toString()))
                        && (Objects.isNull(valueList) || valueList.size() == 0)) {
                    return;
                }

                if (StringUtils.isNotBlank(type) && "date".equalsIgnoreCase(type.toLowerCase())) {
                    value = parseDate(value);
                }
                switch (match) {
                    //=
                    case MongoConstants.query1:
                        operations.add(Criteria.where(key).is(value));
                        break;
                    //>
                    case MongoConstants.query2:
                        operations.add(Criteria.where(key).gt(value));
                        break;
                    //<
                    case MongoConstants.query3:
                        operations.add(Criteria.where(key).lt(value));
                        break;
                    //>=
                    case MongoConstants.query4:
                        operations.add(Criteria.where(key).gte(value));
                        break;
                    //<=
                    case MongoConstants.query5:
                        operations.add(Criteria.where(key).lte(value));
                        break;
                    //in
                    case MongoConstants.query6:
                        operations.add(Criteria.where(key).in(valueList));
                        break;
                    //like
                    case MongoConstants.query7:
                        operations.add(Criteria.where(key).regex(".*?" + value + ".*?"));
                        break;
                    //leftLike
                    case MongoConstants.query8:
                        operations.add(Criteria.where(key).regex(value + ".*?"));
                        break;
                    //rightLike
                    case MongoConstants.query9:
                        operations.add(Criteria.where(key).regex(".*?" + value));
                        break;
                    default:
                        break;

                }
            });
        }
        if (operations.size() > 0) {
            Criteria[] toArray = operations.toArray(new Criteria[operations.size()]);
            Criteria criteria = new Criteria();
            criteria.andOperator(toArray);
            Query query = Query.query(criteria);
            return query;
        } else {
            return Query.query(new Criteria());
        }
    }

    /**
     * 聚合查询
     *
     * @param collectionName 集合名称
     * @param uuid           待查询的主键uuid
     */
    public List<JSONObject> queryAggregate(String collectionName, String uuid) {
        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("uuid").is(uuid)));
        Aggregation aggregation = Aggregation.newAggregation(operations);
        AggregationResults<JSONObject> results = mongoTemplate.aggregate(aggregation, collectionName, JSONObject.class);
        List<JSONObject> mappedResults = results.getMappedResults();
        return mappedResults;
    }

    /**
     * 测试聚合查询
     *
     * @param collectionName 集合名称
     * @return
     */
    public List<JSONObject> test(String collectionName) {
        //聚合查询 按时间统计每天的注册门户用户数
        ProjectionOperation operation = Aggregation.project().andExpression("{$dateToString:{format:'%Y-%m-%d',date:'$registerTime'}}").as("day");
        GroupOperation group = Aggregation.group("day").count().as("count");
//                .sum(aggregationOperationContext -> aggregationOperationContext.getMappedObject(new Document("$toDouble", "$attr3"))).as("sum");
        AggregationOperation sort = Aggregation.sort(Sort.by("day").descending());
        Aggregation aggregation = Aggregation.newAggregation(operation, group, sort);
        AggregationResults<JSONObject> aggregationResults = mongoTemplate.aggregate(aggregation, collectionName, JSONObject.class);
        List<JSONObject> mappedResults = aggregationResults.getMappedResults();
        return mappedResults;
    }

    private static String[] parsePatterns = {
            "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
            "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
            "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};

    /**
     * 日期型字符串转化为日期 格式
     */
    public static Date parseDate(Object str) {
        if (str == null) {
            return null;
        }
        try {
            return DateUtils.parseDate(str.toString(), parsePatterns);
        } catch (ParseException e) {
            return null;
        }
    }
}
